Põhjalik juhend Pandas DataFrame'ide optimeerimiseks mälukasutuse ja jõudluse osas, hõlmates andmetüüpe, indekseerimist ja täiustatud tehnikaid.
Pandas DataFrame'i optimeerimine: mälukasutus ja jõudluse häälestamine
Pandas on võimas Pythoni teek andmetega manipuleerimiseks ja analüüsiks. Suurte andmehulkadega töötades võivad Pandas DataFrame'id aga tarbida märkimisväärse hulga mälu ja olla aeglase jõudlusega. See artikkel pakub põhjalikku juhendit Pandas DataFrame'ide optimeerimiseks nii mälukasutuse kui ka jõudluse osas, võimaldades teil suuremaid andmehulki tõhusamalt töödelda.
Pandas DataFrame'ide mälukasutuse mõistmine
Enne optimeerimistehnikatesse süvenemist on oluline mõista, kuidas Pandas DataFrame'id andmeid mällu salvestavad. DataFrame'i igal veerul on kindel andmetüüp, mis määrab selle väärtuste salvestamiseks vajaliku mälu hulga. Levinud andmetüübid on järgmised:
- int64: 64-bitised täisarvud (vaikimisi täisarvudele)
- float64: 64-bitised ujukomaarvud (vaikimisi ujukomaarvudele)
- object: Pythoni objektid (kasutatakse stringide ja segatüüpi andmete jaoks)
- category: Kategoorilised andmed (tõhus korduvate väärtuste jaoks)
- bool: Tõeväärtused (True/False)
- datetime64: Kuupäeva ja kellaaja väärtused
Andmetüüp object on sageli kõige mälumahukam, kuna see salvestab viiteid Pythoni objektidele, mis võivad olla oluliselt suuremad kui primitiivsed andmetüübid nagu täisarvud või ujukomaarvud. Stringid, isegi lühikesed, tarbivad object-tüüpi salvestatuna palju rohkem mälu kui vaja. Samamoodi raiskab mälu int64 kasutamine, kui int32 piisaks.
Näide: DataFrame'i mälukasutuse uurimine
DataFrame'i mälukasutuse uurimiseks saate kasutada meetodit memory_usage():
import pandas as pd
import numpy as np
data = {
'col1': np.random.randint(0, 1000, 100000),
'col2': np.random.rand(100000),
'col3': ['A', 'B', 'C'] * (100000 // 3 + 1)[:100000],
'col4': ['This is a long string'] * 100000
}
df = pd.DataFrame(data)
memory_usage = df.memory_usage(deep=True)
print(memory_usage)
print(df.dtypes)
Argument deep=True tagab, et objektide (nagu stringid) mälukasutus arvutatakse täpselt. Ilma deep=True argumendita arvutatakse ainult viitade mälu, mitte nende aluseks olevate andmete oma.
Andmetüüpide optimeerimine
Üks tõhusamaid viise mälukasutuse vähendamiseks on valida oma DataFrame'i veergudele kõige sobivamad andmetüübid. Siin on mõned levinud tehnikad:
1. Numbriliste andmetüüpide vähendamine (downcasting)
Kui teie täisarvu- või ujukomaarvude veerud ei vaja täielikku 64-bitist täpsust, saate need vähendada väiksemateks andmetüüpideks nagu int32, int16, float32 või float16. See võib oluliselt vähendada mälukasutust, eriti suurte andmehulkade puhul.
Näide: Võtame näiteks vanust tähistava veeru, mis tõenäoliselt ei ületa 120. Selle salvestamine int64-na on raiskamine; int8 (vahemik -128 kuni 127) oleks sobivam.
def downcast_numeric(df):
"""Vähendab numbrilised veerud väikseimale võimalikule andmetüübile."""
for col in df.columns:
if pd.api.types.is_integer_dtype(df[col]):
df[col] = pd.to_numeric(df[col], downcast='integer')
elif pd.api.types.is_float_dtype(df[col]):
df[col] = pd.to_numeric(df[col], downcast='float')
return df
df = downcast_numeric(df.copy())
print(df.memory_usage(deep=True))
print(df.dtypes)
Funktsiooni pd.to_numeric() koos argumendiga downcast kasutatakse automaatselt väikseima võimaliku andmetüübi valimiseks, mis suudab veerus olevaid väärtusi esitada. copy() väldib algse DataFrame'i muutmist. Enne andmetüübi vähendamist kontrollige alati oma andmete väärtuste vahemikku, et tagada, et te ei kaota informatsiooni.
2. Kategooriliste andmetüüpide kasutamine
Kui veerg sisaldab piiratud arvu unikaalseid väärtusi, saate selle teisendada category andmetüübiks. Kategoorilised andmetüübid salvestavad iga unikaalse väärtuse ainult üks kord ja kasutavad seejärel veerus olevate väärtuste esitamiseks täisarvukoode. See võib oluliselt vähendada mälukasutust, eriti veergude puhul, kus on suur osa korduvaid väärtusi.
Näide: Võtame näiteks riigikoode tähistava veeru. Kui tegelete piiratud hulga riikidega (nt ainult Euroopa Liidu riigid), on selle salvestamine kategooriana palju tõhusam kui stringidena.
def optimize_categories(df):
"""Teisendab madala kardinaalsusega object-tüüpi veerud kategooriliseks tüübiks."""
for col in df.columns:
if df[col].dtype == 'object':
num_unique_values = len(df[col].unique())
num_total_values = len(df[col])
if num_unique_values / num_total_values < 0.5:
df[col] = df[col].astype('category')
return df
df = optimize_categories(df.copy())
print(df.memory_usage(deep=True))
print(df.dtypes)
See kood kontrollib, kas unikaalsete väärtuste arv object-tüüpi veerus on alla 50% koguväärtustest. Kui jah, siis teisendab see veeru kategooriliseks andmetüübiks. 50% lävi on meelevaldne ja seda saab kohandada vastavalt teie andmete spetsiifilistele omadustele. See lähenemine on kõige kasulikum, kui veerg sisaldab palju korduvaid väärtusi.
3. Object andmetüübi vältimine stringide puhul
Nagu varem mainitud, on object andmetüüp sageli kõige mälumahukam, eriti kui seda kasutatakse stringide salvestamiseks. Kui võimalik, proovige vältida object andmetüüpide kasutamist stringiveergude jaoks. Madala kardinaalsusega stringide jaoks on eelistatud kategoorilised tüübid. Kui kardinaalsus on kõrge, kaaluge, kas stringe saab esitada numbriliste koodidega või kas stringiandmeid saab üldse vältida.
Kui teil on vaja veerus stringioperatsioone teha, peate võib-olla hoidma seda object-tüüpi, kuid kaaluge, kas neid operatsioone saab teha eelnevalt ja seejärel teisendada andmed tõhusamale tüübile.
4. Kuupäeva ja kellaaja andmed
Kasutage kuupäeva ja kellaaja teabe jaoks andmetüüpi `datetime64`. Veenduge, et eraldusvõime oleks sobiv (nanosekundi eraldusvõime võib olla ebavajalik). Pandas käsitleb aegrea andmeid väga tõhusalt.
DataFrame'i operatsioonide optimeerimine
Lisaks andmetüüpide optimeerimisele saate Pandas DataFrame'ide jõudlust parandada ka nendega tehtavate operatsioonide optimeerimisega. Siin on mõned levinud tehnikad:
1. Vektoriseerimine
Vektoriseerimine on protsess, kus operatsioone teostatakse tervete massiivide või veergudega korraga, selle asemel et itereerida üle üksikute elementide. Pandas on vektoriseeritud operatsioonide jaoks kõrgelt optimeeritud, seega nende kasutamine võib jõudlust oluliselt parandada. Vältige võimaluse korral otseseid tsükleid. Pandase sisseehitatud funktsioonid on üldiselt palju kiiremad kui samaväärsed Pythoni tsüklid.
Näide: Selle asemel et itereerida läbi veeru iga väärtuse ruudu arvutamiseks, kasutage funktsiooni pow():
# Ebatõhus (kasutades tsüklit)
import time
start_time = time.time()
results = []
for value in df['col2']:
results.append(value ** 2)
df['col2_squared_loop'] = results
end_time = time.time()
print(f"Tsükli aeg: {end_time - start_time:.4f} sekundit")
# Tõhus (kasutades vektoriseerimist)
start_time = time.time()
df['col2_squared_vectorized'] = df['col2'] ** 2
end_time = time.time()
print(f"Vektoriseeritud aeg: {end_time - start_time:.4f} sekundit")
Vektoriseeritud lähenemine on tavaliselt kordades kiirem kui tsüklipõhine lähenemine.
2. `apply()` kasutamine ettevaatlikult
Meetod apply() võimaldab teil rakendada funktsiooni DataFrame'i igale reale või veerule. See on aga üldiselt aeglasem kui vektoriseeritud operatsioonid, kuna see hõlmab Pythoni funktsiooni kutsumist iga elemendi jaoks. Kasutage apply() ainult siis, kui vektoriseeritud operatsioonid ei ole võimalikud.
Kui peate kasutama `apply()`, proovige rakendatavat funktsiooni võimalikult palju vektoriseerida. Kaaluge Numba `jit` dekoraatori kasutamist, et kompileerida funktsioon masinkoodiks, mis parandab jõudlust märkimisväärselt.
from numba import jit
@jit(nopython=True)
def my_function(x):
return x * 2 # Näidisfunktsioon
df['col2_applied'] = df['col2'].apply(my_function)
3. Veergude tõhus valimine
DataFrame'ist veergude alamhulga valimisel kasutage optimaalse jõudluse saavutamiseks järgmisi meetodeid:
- Otsene veeruvalik:
df[['col1', 'col2']](kiireim mõne veeru valimiseks) - Tõeväärtustega indekseerimine:
df.loc[:, [True if col.startswith('col') else False for col in df.columns]](kasulik veergude valimiseks tingimuse alusel)
Vältige df.filter() kasutamist regulaaravaldistega veergude valimiseks, kuna see võib olla aeglasem kui teised meetodid.
4. Ühendamiste (joins) ja liitmiste (merges) optimeerimine
DataFrame'ide ühendamine ja liitmine võib olla arvutuslikult kulukas, eriti suurte andmehulkade puhul. Siin on mõned näpunäited ühendamiste ja liitmiste optimeerimiseks:
- Kasutage sobivaid ühendusvõtmeid: Veenduge, et ühendusvõtmetel oleks sama andmetüüp ja et need oleksid indekseeritud.
- Määrake ühenduse tüüp: Kasutage vastavalt oma vajadustele sobivat ühenduse tüüpi (nt
inner,left,right,outer). Sisemine ühendus (inner join) on üldiselt kiirem kui välimine ühendus (outer join). - Kasutage `merge()` `join()` asemel: Funktsioon
merge()on mitmekülgsem ja sageli kiirem kui meetodjoin().
Näide:
df1 = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'value1': [1, 2, 3, 4]})
df2 = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'value2': [5, 6, 7, 8]})
# Tõhus sisemine ühendus
df_merged = pd.merge(df1, df2, on='key', how='inner')
print(df_merged)
5. DataFrame'ide ebavajaliku kopeerimise vältimine
Paljud Pandase operatsioonid loovad DataFrame'idest koopiaid, mis võib olla mälumahukas ja aeganõudev. Ebavajaliku kopeerimise vältimiseks kasutage võimaluse korral argumenti inplace=True või määrake operatsiooni tulemus tagasi algsele DataFrame'ile. Olge inplace=True kasutamisel väga ettevaatlik, kuna see võib vigu varjata ja silumist raskendada. Sageli on turvalisem uuesti määrata, isegi kui see on veidi vähem jõudluslik.
Näide:
# Ebatõhus (loob koopia)
df_filtered = df[df['col1'] > 500]
# Tõhus (muudab algset DataFrame'i kohapeal - ETTEVAATUST)
df.drop(df[df['col1'] <= 500].index, inplace=True)
#TURVALISEM - määrab uuesti, väldib inplace'i
df = df[df['col1'] > 500]
6. Tükeldamine ja itereerimine
Äärmiselt suurte andmehulkade puhul, mis mällu ei mahu, kaaluge andmete töötlemist tükkidena. Kasutage failidest andmete lugemisel parameetrit chunksize. Itereerige läbi tükkide ja tehke oma analüüs igal tükil eraldi. See nõuab hoolikat planeerimist, et tagada analüüsi korrektsus, kuna mõned operatsioonid nõuavad kogu andmehulga korraga töötlemist.
# Loe CSV-d tükkidena
for chunk in pd.read_csv('large_data.csv', chunksize=100000):
# Töötle iga tükki
print(chunk.shape)
7. Daski kasutamine paralleeltöötluseks
Dask on paralleelarvutuse teek, mis integreerub sujuvalt Pandasega. See võimaldab teil töödelda suuri DataFrame'e paralleelselt, mis võib jõudlust oluliselt parandada. Dask jaotab DataFrame'i väiksemateks partitsioonideks ja jaotab need mitme tuuma või masina vahel.
import dask.dataframe as dd
# Loo Daski DataFrame
ddf = dd.read_csv('large_data.csv')
# Teosta operatsioone Daski DataFrame'il
ddf_filtered = ddf[ddf['col1'] > 500]
# Arvuta tulemus (see käivitab paralleelarvutuse)
result = ddf_filtered.compute()
print(result.head())
Indekseerimine kiiremateks otsinguteks
Veerule indeksi loomine võib oluliselt kiirendada otsingu- ja filtreerimisoperatsioone. Pandas kasutab indekseid, et kiiresti leida kindlale väärtusele vastavad read.
Näide:
# Määra 'col3' indeksiks
df = df.set_index('col3')
# Kiirem otsing
value = df.loc['A']
print(value)
# Lähtesta indeks
df = df.reset_index()
Siiski võib liiga paljude indeksite loomine suurendada mälukasutust ja aeglustada kirjutamisoperatsioone. Ainult looge indekseid veergudele, mida kasutatakse sageli otsinguteks või filtreerimiseks.
Muud kaalutlused
- Riistvara: Kaaluge oma riistvara (protsessor, RAM, SSD) uuendamist, kui töötate pidevalt suurte andmehulkadega.
- Tarkvara: Veenduge, et kasutate Pandase uusimat versiooni, kuna uuemad versioonid sisaldavad sageli jõudluse parandusi.
- Profileerimine: Kasutage profileerimisvahendeid (nt
cProfile,line_profiler), et tuvastada oma koodis jõudluse kitsaskohad. - Andmete salvestusvorming: Kaaluge tõhusamate andmesalvestusvormingute nagu Parquet või Feather kasutamist CSV asemel. Need vormingud on veerupõhised ja sageli tihendatud, mis viib väiksemate failisuuruste ja kiiremate lugemis-/kirjutamisaegadeni.
Kokkuvõte
Pandas DataFrame'ide optimeerimine mälukasutuse ja jõudluse osas on suurte andmehulkadega tõhusaks töötamiseks ülioluline. Valides sobivad andmetüübid, kasutades vektoriseeritud operatsioone ja indekseerides oma andmeid tõhusalt, saate oluliselt vähendada mälutarvet ja parandada jõudlust. Ärge unustage oma koodi profileerida, et tuvastada jõudluse kitsaskohad, ja kaaluge tükeldamise või Daski kasutamist eriti suurte andmehulkade puhul. Neid tehnikaid rakendades saate avada Pandase täieliku potentsiaali andmeanalüüsiks ja -manipulatsiooniks.